package umg.os.devices.filesystem.logical;

import java.io.IOException;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.List;

import umg.os.devices.filesystem.basic.BasicFileSystem;
import umg.os.devices.filesystem.basic.BasicFileSystem.DataOversizedException;
import umg.os.devices.filesystem.basic.HDDHandler.VolumeUnavailableException;

public class File {

	private FileControlBlock fcb;
	private BasicFileSystem bfs;
	private final List<Integer> dataSectors;
	private boolean edited; 
	private List<Edition> editions;

	protected File(BasicFileSystem bfs, FileControlBlock fcb, List<Integer> dataSectors) throws IOException, VolumeUnavailableException, DataOversizedException {
		this.bfs = bfs;
		this.fcb = fcb;
		this.dataSectors = dataSectors;
		fcb.setOpening(System.currentTimeMillis());
		editions = new ArrayList<Edition>();
	}

	void setFirstSector(int firstSector) {
		if (fcb.getFirstSector() == 0) {
			throw new UnsupportedOperationException("A directory cannot contain data!");
		} else if (fcb.getFirstSector() > 0) {
			throw new UnsupportedOperationException("A file cannot change its initial data sector!");
		}
		fcb.setFirstSector(firstSector);
		dataSectors.add(new Integer(firstSector));
	}

	protected FileControlBlock getFcb() {
		return fcb;
	}

	public boolean isEdited() {
		return edited;
	}

	protected void addSector(int sector){
		dataSectors.add(new Integer(sector));
	}

	protected List<Integer> getDataSectors() {
		return dataSectors;
	}

	public byte[] read(int offset, int length) throws IOException, VolumeUnavailableException {
		if (fcb.getFirstSector() == 0) {
			throw new NotFileException();
		}
		if (offset >= fcb.getSize()) {
			throw new IndexOutOfBoundsException("The offset to start reading is bigger than the size of the file.");
		} else if (offset+length >= fcb.getSize()) {
			throw new IndexOutOfBoundsException("The file is not big enough to retrieve the amount of data requested.");
		}
		int 
		firstSectorOfFileToRead = offset/BasicFileSystem.USEFUL_SECTOR_SIZE,
		byteOffsetInFirstSector = offset%BasicFileSystem.USEFUL_SECTOR_SIZE,
		numberOfSectorsToRead = (int) Math.ceil((byteOffsetInFirstSector + length)/BasicFileSystem.USEFUL_SECTOR_SIZE);
		int[] sectorsToRead = new int[numberOfSectorsToRead];
		for (int i = 0; i < numberOfSectorsToRead; i++) {
			sectorsToRead[i] = dataSectors.get(i + firstSectorOfFileToRead);
		}
		byte[] dataToRetrieve = new byte[length]; 
		System.arraycopy(bfs.readContinuouslyFromSectors(sectorsToRead), byteOffsetInFirstSector, dataToRetrieve, 0, length);
		return dataToRetrieve;
	}

	public void save() throws DataOversizedException, IOException, VolumeUnavailableException {
		if (edited) {
			for (Edition edition : editions) {
				edition.saveEdition();
			}
			fcb.setEdition(System.currentTimeMillis());
			//NOTIFY CHANGE
		}
	}

	public void append(byte data[]) {
		editions.add(new AppendEdition(data));
		edited = true;
	}

	public void insert(byte data[]) {


	}

	public void replace(byte data[]) {


	}

	public void delete(int offset, int lengthToDelete) {
		editions.add(new DeleteEdition(offset, lengthToDelete));
		edited = true;
	}

	private abstract class Edition {

		protected final int offset;

		private Edition(int offset) {
			if (offset < 0) {
				throw new InputMismatchException("The offset cannot be negative!");
			}
			this.offset = offset;
		}

		protected abstract void saveEdition() throws IOException, VolumeUnavailableException, DataOversizedException;

	}

	private final class AppendEdition extends Edition {

		private final byte[] data;

		private AppendEdition(byte[] data) {
			super((int) fcb.getSize());
			this.data = data;
		}

		protected void saveEdition() throws IOException, VolumeUnavailableException, DataOversizedException {
			int newSize = (int) (fcb.getSize() + data.length);
			int requiredSectors = (int) Math.ceil((float)newSize / (float)BasicFileSystem.USEFUL_SECTOR_SIZE);
			byte rearrangedData[] = new byte[offset % BasicFileSystem.USEFUL_SECTOR_SIZE + data.length];
			int[] sectorsToWrite = new int[((offset % BasicFileSystem.USEFUL_SECTOR_SIZE != 0)?1:0) + requiredSectors - dataSectors.size() ];
			int 
			counter = 0,
			lastSector = dataSectors.get(dataSectors.size() - 1).intValue(),
			firstSector = dataSectors.get(dataSectors.size() - 1).intValue();
			if (offset % BasicFileSystem.USEFUL_SECTOR_SIZE != 0) {
				System.arraycopy(bfs.readSector(lastSector), 0, rearrangedData, 0, offset % BasicFileSystem.USEFUL_SECTOR_SIZE); 
				sectorsToWrite[0] = lastSector;
				counter++;
			}
			System.arraycopy(data, 0, rearrangedData, offset % BasicFileSystem.USEFUL_SECTOR_SIZE, data.length);
			while (requiredSectors > dataSectors.size()) {
				lastSector = bfs.reserveSector();
				sectorsToWrite[counter] = lastSector;
				bfs.setSectorPointer(dataSectors.get(dataSectors.size() - 1).intValue(), lastSector);
				dataSectors.add(new Integer(lastSector));
				counter++;
			} 
			bfs.writeInLinkedSectors(firstSector, rearrangedData);
		}

	}

	@SuppressWarnings("unused")
	private final class InsertEdition extends Edition {

		final byte[] data;

		private InsertEdition(int offset, byte[] data) {
			super(offset);
			this.data = data;
		}

		protected void saveEdition() {

		}

	}

	@SuppressWarnings("unused")
	private final class ReplaceEdition extends Edition {

		final byte[] data;

		private ReplaceEdition(int offset, byte[] data) {
			super(offset);
			this.data = data;
		}

		protected void saveEdition() {

		}

	}

	private final class DeleteEdition extends Edition {

		private final int lengthToDelete;

		private DeleteEdition(int offset, int lengthToDelete) {
			super(offset);
			if (lengthToDelete < 1) {
				throw new InputMismatchException("The length to delete cannot be less than one!");
			}
			this.lengthToDelete = lengthToDelete;
		}

		protected void saveEdition() throws IOException, VolumeUnavailableException, DataOversizedException {
			int firstFileSectorAffected = offset / BasicFileSystem.USEFUL_SECTOR_SIZE;
			byte[] affectedData = bfs.readContinuouslyFromLinkedSectors(dataSectors.get(firstFileSectorAffected).intValue());
			byte[] remainingData = new byte[offset + affectedData.length - lengthToDelete];
			System.arraycopy(affectedData, 0, remainingData, 0, offset % BasicFileSystem.USEFUL_SECTOR_SIZE);
			System.arraycopy(affectedData, (offset % BasicFileSystem.USEFUL_SECTOR_SIZE) + lengthToDelete, remainingData, offset, affectedData.length - offset - lengthToDelete);
			bfs.writeInLinkedSectors(dataSectors.get(firstFileSectorAffected).intValue(), remainingData);
			int requiredSectors = (int) (Math.ceil((float)(affectedData.length - lengthToDelete) / (float)BasicFileSystem.USEFUL_SECTOR_SIZE) + (offset / BasicFileSystem.USEFUL_SECTOR_SIZE));
			fcb.setSize(fcb.getSize() - lengthToDelete);
			bfs.clearPointerOfLinkedSectors(dataSectors.get(firstFileSectorAffected + requiredSectors).intValue());
			//TODO notify to save the logical metadata
		}

	}

	

}
